<?php

namespace paper\component;

use paper\http\Request;
use paper\http\Router;
use paper\http\RoutingRules;

class RouteParser implements \paper\constraint\RouteParser
{
    private Router $router;

    public function __construct()
    {
    }

    public function parse(Request $request, Router $router, ?RoutingRules $routingRules): \Closure|array|bool
    {
        if (!$routingRules)
            return false;

        $this->router = $router;

        $path = $request->path();


        if ($route = $this->parseRoutes($path, $routingRules->getRoutes())) {
            return $this->route2callable($route);
        }

        return false;
//        $path = substr($path, 1);
//
//        if (str_ends_with("/", $path)) {
//            $path .= "index";
//        }
//        $urlArr = explode("/", $path, 2);
//
//        $controllerName = $urlArr[0] ?? "index";
//        $actionName = $urlArr[1] ?? "index";
////        var_dump($controllerName);
//        $controllerClass = $this->buildControllerClass($controllerName);
//        $actionMethod = $this->camelize($actionName);
//        return [$controllerClass, $actionMethod];
    }

    private function parseRoutes(string $path, array $routes)
    {
        if (str_starts_with($path, '/'))
            $path = substr($path, 1);
//        echo "[".$path."]\r\n";
        foreach ($routes as $route) {
            $prefix = $route['prefix'];

            $pattern = $route['pattern'];
            if ($pattern instanceof \Closure) {
                if ($prefix != null && !str_starts_with($path, $prefix))
                    continue;
                //创建子级规则
                $routeingRules = new RoutingRules();
                //$continue表示未匹配成功是否继续
                $continue = $pattern($routeingRules);
                $routes = $routeingRules->getRoutes();
                $sub = substr($path, mb_strlen($prefix));
                if ($match = $this->parseRoutes($sub, $routes)) {
                    $route['middlewares'] = array_merge($route['middlewares'], $match['middlewares']);
                    $route['withoutMiddlewares'] = array_merge($route['withoutMiddlewares'], $match['withoutMiddlewares']);
                    $route['match'] = $match['match'];
                    $route['to'] = $match['to'];
                    return $route;
                } else {
                    if ($continue)
                        continue;
                    return false;
                }
            } else {
                if ($match = $this->match($path, $route)) {
                    return $match;
                }
            }
        }
        return false;
    }

    private function match($path, $route): array|bool
    {
        $preg = $this->pattern2preg($route['pattern']);

        if (preg_match($preg, $path, $m)) {
            $route['match'] = $m;
            \request()->parameters = $m;
            return $route;
        }
        return false;
    }

    private function pattern2preg($pattern): string
    {
        $tmp = explode("/", $pattern);
        $pregPattern = '';
        foreach ($tmp as $item) {
            if ($item == "")
                continue;
            if (str_starts_with($item, "{") && str_ends_with($item, "}")) {
                $pattern = substr($item, 1, strlen($item) - 2);
                if (str_starts_with($pattern, "?")) {
                    $name = substr($pattern, 1);
                    $pregPattern .= '(?P<' . $name . '>[^/]+)?/?';
                } else {
                    $name = $pattern;
                    $pregPattern .= '(?P<' . $name . '>[^/]+)/?';
                }
            } else {
                $pregPattern .= preg_quote($item) . '/?';
            }
        }

        return '#^' . $pregPattern . '$#';
    }

    private function route2callable(array $route): bool|array|\Closure
    {
        $to = $route["to"];
        $this->router->addMiddlewares($route['middlewares']);
        $this->router->removeMiddlewares($route['withoutMiddlewares']);
        if ($to instanceof \Closure) {
            return $to;
        } elseif (is_string($to) || $to instanceof \Stringable) {
            if (str_contains($to, "@")) {
                if (str_contains($to, "[")) {
                    $to = preg_replace_callback("/\[([a-zA-Z0-9]+)]/", function ($m) use ($route) {
                        return $route['match'][$m[1]] ?? 'index';
                    }, $to);
                }
//                var_dump($route['match']);
                $arr = explode("@", $to);
                $controllerClass = $this->buildControllerClass($route['namespace'], $arr[0]);
                $actionMethod = $this->camelize($arr[1]);
                return [$controllerClass, $actionMethod];
            }

        }
        return false;
    }

    private function buildControllerClass($namespace, string $controllerName): string
    {
        if ($namespace)
            $namespace .= '\\';
        return '\\app\\controller\\' . $namespace . $this->camelize($controllerName) . 'Controller';
    }


    function camelize($words, $separator = '_'): string
    {
        $words = str_replace($separator, " ", strtolower($words));
        return ucfirst(str_replace(" ", "", ucwords($words)));
    }
}